-
-
Notifications
You must be signed in to change notification settings - Fork 4.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: provide new Component
type that represents the shape of components
#11775
Conversation
🦋 Changeset detectedLatest commit: 984b5c5 The changes in this PR will be included in the next version bump. This PR includes changesets to release 1 package
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
…nents In Svelte 3 and 4, components were classes under the hood, and the base class was `SvelteComponent`. This class was also used in language tools to properly type check the template code. In Svelte 5, components are functions. To give people a way to extend them programmatically, it would be good to expose the actual shape of components. This is why this PR introduces a new `Component` type. For backwards compatibility reasons, we can't just get rid of the old class-based types. We also need to ensure that language tools can work with both the new and old types: There are many libraries out there that provide `d.ts` files with type definitions written using the class types - these should not error. That's why there's an accompagning language tools PR (sveltejs/language-tools#2380) that's doing the heavy lifting: Instead of generating classes, it now generates a constant and an interfaces and uses Typescript's declaration merging feature to provide both so we can declare a component export as being both a class and a function. That ensures that people can still instantiate them with `new` (which they can do if they use the `legacy.componentApi` compiler option), and it also ensure we don't need to adjust any other code generation mechanisms in language tools yet - from a language tools perspective, classes are still the norm. But through exposing the default export as being _also_ callable as a function we can in a future Svelte version, where classes/the Svelte 4 syntax are removed completely, seamlessly switch over to using functions in the code generation, too, and the `d.ts` files generated up until that point will support it because of the dual shape. This way we have both backwards and forwards compatibility.
d6c60bb
to
c9a19e4
Compare
/** Does not exist at runtime, for typing capabilities only. DO NOT USE */ | ||
z_$$bindings?: Bindings; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we just use @deprecated
to make sure this sinks to the bottom, rather than the z_
prefix?
/** Does not exist at runtime, for typing capabilities only. DO NOT USE */ | |
z_$$bindings?: Bindings; | |
/** @deprecated Does not exist at runtime, for typing capabilities only. DO NOT USE */ | |
$$bindings?: Bindings; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I prefer we don't because using it would print out "this is deprecated" warnings from svelte-check
from the generated locations, and filtering those out is a bit tough. I'm also not sure if all IDEs move it to the bottom. Lastly, this is a very rare thing to show up anyway because it's on the function object, not the instance type.
Co-authored-by: Rich Harris <[email protected]>
How to use this? Shows 'svelte' has no exported member named Component. this was working until I updated: import type { ComponentType } from 'svelte';
export type IconType = {
[key: string]: ComponentType<Icon>;
}; but replacing ComponentType with Component doesn't work. |
Svelte 5 uses functions to define components under the hood. This should be represented in the types. We can't just switch to using functions though because d.ts files created from Svelte 4 libraries should still work, and those contain classes. So we need interop between functions and classes. The idea is therefore: Svelte 5 creates a default export which is both a function and a class constructor Various places are adjusted to support the new default exports Also see sveltejs/svelte#11775
I have a similar problem with @aadtyaraj01 with VSCode. When I use the following example code:
I get the following type waring under Argument of type 'Component<{}, {}, ""> | undefined' is not assignable to parameter of type 'ConstructorOfATypedSvelteComponent | null | undefined'. Possible causes:
|
|
I just released the corresponding updates to the VS Code extension and svelte-check, please try again with their latest versions |
@dummdidumm Thanks for a quick fix. My problem is solved. |
I still have this issue in intellij Svelte: Argument of type typeof Splashscreen__SvelteComponent_ is not assignable to parameter of type Component<{}, {}, "">
Type typeof Splashscreen__SvelteComponent_ provides no match for the signature
(internal: unknown, props: {}): { $on?(type: string, callback: (e: any) => void): () => void; $set?(props: Partial<{}>): void; } //EDIT |
Hovering over the
Currently when I run
Does this mean library creators need to add the above lines to |
How does your index.js look like? |
For example:
|
No you don't need to do anything when using svelte-package. We should add that to the hover info |
When I try to pass a component as prop using the latest versions of svelte and svelte-check, I now get problems with the binding parameter:
Example:
IconWrapper.svelte
|
can confirm, same issue <script lang="ts">
import SceneStore from '$lib/SceneStore.svelte';
import Logo from '$assets/Logo_MagicTale_Mobile.png';
import Login, {load} from "./Login.svelte";
$effect(() => {
setTimeout(() => {
SceneStore.changeScene(Login, load)
}, 2000)
})
</script> while Login does not use $props() |
Exactly what I saw, I have my imported component typed as: (alias) const Overview: __sveltets_2_IsomorphicComponent<Record<string, never>, {
[evt: string]: CustomEvent<any>;
}, {}, Record<string, any>, string>
export Overview Therefore VS code gives me:
|
Not sure how |
The current type narrows the binding type to `""` by default, which means "no bindings on this component". While this is the common case, it makes it very cumbersome to use the `Component` type because legacy components are of type `string` and as soon as you have bindings, the type is something like `"foo" | "bar"` which _also_ is not assignable to `""` which is semantically wrong, because you should be able to assign a component that can have bindings to a type that accepts none. The pragmatic solution is to change the binding type to allow `string`, which means someone theoretically could use bindings with a component that doesn't have bindings: ```svelte <script> let component: Component<{ prop: boolean }> = IAcceptNoBindings; </script> <!-- allowed but should be a type error --> <svelte:component this={component} bind:prop={foo} /> ``` But this is a) rare anyway and b) can be caught at runtime This came up in comments of #11775
The current type narrows the binding type to `""` by default, which means "no bindings on this component". While this is the common case, it makes it very cumbersome to use the `Component` type because legacy components are of type `string` and as soon as you have bindings, the type is something like `"foo" | "bar"` which _also_ is not assignable to `""` which is semantically wrong, because you should be able to assign a component that can have bindings to a type that accepts none. The pragmatic solution is to change the binding type to allow `string`, which means someone theoretically could use bindings with a component that doesn't have bindings: ```svelte <script> let component: Component<{ prop: boolean }> = IAcceptNoBindings; </script> <!-- allowed but should be a type error --> <svelte:component this={component} bind:prop={foo} /> ``` But this is a) rare anyway and b) can be caught at runtime This came up in comments of #11775
I've got a question related to this new type feature. import type { Component, SvelteComponent } from "svelte";
import Button from "./Button.svelte"; // Svelte v5 component
type IsComponent = Button extends Component ? true : false;
//. ^ false - ❌ not expected
type IsComponentWithTypeOf = typeof Button extends Component ? true : false;
//. ^ true - ✅ expected
type IsSvelteComponent = Button extends SvelteComponent ? true : false;
//. ^ true - ❌ not expected
type IsSvelteComponentWithTypeOf = typeof Button extends SvelteComponent ? true : false;
//. ^ false - ✅ expected I think the latest version of Source code of Button.svelte<script lang="ts">
import type { Snippet } from 'svelte';
import type { HTMLButtonAttributes } from 'svelte/elements';
interface Props extends HTMLButtonAttributes {
/**
* Is this the principal call to action on the page?
*/
primary?: boolean;
/**
* What background color to use
*/
backgroundColor?: string;
/**
* How large should the button be?
*/
size?: 'small' | 'medium' | 'large';
/**
* Content of the button
*/
children?: Snippet;
}
const {
primary = true,
backgroundColor,
size = 'medium',
children,
...buttonProps
}: Props = $props();
</script>
<button
type="button"
class:primary
class={size}
style={backgroundColor ? `background-color: ${backgroundColor}` : ''}
{...buttonProps}
>
{#if children}
{@render children()}
{/if}
</button>
<style>
button {
font-family: 'Nunito Sans', 'Helvetica Neue', Helvetica, Arial, sans-serif;
font-weight: 700;
border: 0;
border-radius: 3em;
cursor: pointer;
display: inline-block;
line-height: 1;
color: #333;
background-color: transparent;
box-shadow: rgba(0, 0, 0, 0.15) 0px 0px 0px 1px inset;
}
.primary {
color: white;
background-color: #1ea7fd;
box-shadow: unset;
}
.small {
font-size: 12px;
padding: 10px 16px;
}
.medium {
font-size: 14px;
padding: 11px 20px;
}
.large {
font-size: 16px;
padding: 12px 24px;
}
</style> QuestionIs there anything I can do to not use What I am trying to achieve is to create a type helper |
In Svelte 3 and 4, components were classes under the hood, and the base class was
SvelteComponent
. This class was also used in language tools to properly type check the template code. In Svelte 5, components are functions. To give people a way to extend them programmatically, it would be good to expose the actual shape of components. This is why this PR introduces a newComponent
type.For backwards compatibility reasons, we can't just get rid of the old class-based types. We also need to ensure that language tools can work with both the new and old types: There are many libraries out there that provide
d.ts
files with type definitions written using the class types - these should not error.That's why there's an accompagning language tools PR (sveltejs/language-tools#2380) that's doing the heavy lifting: Instead of generating classes, it now generates a constant and an interfaces and uses Typescript's declaration merging feature to provide both so we can declare a component export as being both a class and a function. That ensures that people can still instantiate them with
new
(which they can do if they use thelegacy.componentApi
compiler option), and it also ensure we don't need to adjust any other code generation mechanisms in language tools yet - from a language tools perspective, classes are still the norm. But through exposing the default export as being also callable as a function we can in a future Svelte version, where classes/the Svelte 4 syntax are removed completely, seamlessly switch over to using functions in the code generation, too, and thed.ts
files generated up until that point will support it because of the dual shape. This way we have both backwards and forwards compatibility.Closes #11472
Before submitting the PR, please make sure you do the following
feat:
,fix:
,chore:
, ordocs:
.Tests and linting
pnpm test
and lint the project withpnpm lint